/* Copyright Statement:
 *
 * This software/firmware and related documentation ("AutoChips Software") are
 * protected under relevant copyright laws. The information contained herein is
 * confidential and proprietary to AutoChips Inc. and/or its licensors. Without
 * the prior written permission of AutoChips inc. and/or its licensors, any
 * reproduction, modification, use or disclosure of AutoChips Software, and
 * information contained herein, in whole or in part, shall be strictly
 * prohibited.
 *
 * AutoChips Inc. (C) 2023. All rights reserved.
 *
 * BY OPENING THIS FILE, RECEIVER HEREBY UNEQUIVOCALLY ACKNOWLEDGES AND AGREES
 * THAT THE SOFTWARE/FIRMWARE AND ITS DOCUMENTATIONS ("AUTOCHIPS SOFTWARE")
 * RECEIVED FROM AUTOCHIPS AND/OR ITS REPRESENTATIVES ARE PROVIDED TO RECEIVER
 * ON AN "AS-IS" BASIS ONLY. AUTOCHIPS EXPRESSLY DISCLAIMS ANY AND ALL
 * WARRANTIES, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE OR
 * NONINFRINGEMENT. NEITHER DOES AUTOCHIPS PROVIDE ANY WARRANTY WHATSOEVER WITH
 * RESPECT TO THE SOFTWARE OF ANY THIRD PARTY WHICH MAY BE USED BY,
 * INCORPORATED IN, OR SUPPLIED WITH THE AUTOCHIPS SOFTWARE, AND RECEIVER AGREES
 * TO LOOK ONLY TO SUCH THIRD PARTY FOR ANY WARRANTY CLAIM RELATING THERETO.
 * RECEIVER EXPRESSLY ACKNOWLEDGES THAT IT IS RECEIVER'S SOLE RESPONSIBILITY TO
 * OBTAIN FROM ANY THIRD PARTY ALL PROPER LICENSES CONTAINED IN AUTOCHIPS
 * SOFTWARE. AUTOCHIPS SHALL ALSO NOT BE RESPONSIBLE FOR ANY AUTOCHIPS SOFTWARE
 * RELEASES MADE TO RECEIVER'S SPECIFICATION OR TO CONFORM TO A PARTICULAR
 * STANDARD OR OPEN FORUM. RECEIVER'S SOLE AND EXCLUSIVE REMEDY AND AUTOCHIPS'S
 * ENTIRE AND CUMULATIVE LIABILITY WITH RESPECT TO THE AUTOCHIPS SOFTWARE
 * RELEASED HEREUNDER WILL BE, AT AUTOCHIPS'S OPTION, TO REVISE OR REPLACE THE
 * AUTOCHIPS SOFTWARE AT ISSUE, OR REFUND ANY SOFTWARE LICENSE FEES OR SERVICE
 * CHARGE PAID BY RECEIVER TO AUTOCHIPS FOR SUCH AUTOCHIPS SOFTWARE AT ISSUE.
 */

/*!
 * @file ac780x_gpio.c
 *
 * @brief This file provides gpio integration functions.
 *
 */

/* ===========================================  Includes  =========================================== */
#include "ac780x_gpio_reg.h"

/* ============================================  Define  ============================================ */
#define GPIO_BIT0       (1UL)
#define GPIO_INT_NUM    (5UL)

/* GPIO related info */
static const IRQn_Type s_gpioIRQ[GPIO_INT_NUM] =  \
{EXTI0_IRQn, EXTI1_IRQn, EXTI2_IRQn, EXTI3_8_IRQn, EXTI9_15_IRQn};

/*!< PIN MUX register define */
#define PMUX0                                     ((uint32_t *)(&(PMUX->PINMUX0)))
#define PMUX1                                     ((uint32_t *)(&(PMUX->PINMUX1)))
#define PMUX2                                     ((uint32_t *)(&(PMUX->PINMUX2)))
#define PMUX3                                     ((uint32_t *)(&(PMUX->PINMUX3)))
#define PMUX4                                     ((uint32_t *)(&(PMUX->PINMUX4)))
#define PMUX5                                     ((uint32_t *)(&(PMUX->PINMUX5)))

/* ===========================================  Typedef  ============================================ */

/* ==========================================  Variables  =========================================== */
DeviceCallback_Type g_gpioCallbackArray[GPIO_INT_NUM] = {(DeviceCallback_Type)NULL};

/* ====================================  Functions declaration  ===================================== */
static void GPIO_SetPinExtIntSource(GPIO_Type *GPIOx, GPIO_PinType GPIO_Pin);
static GPIO_Type * GPIO_GetPendingExtIntSource(GPIO_PinType GPIO_Pin);
static void GPIO_SetPinFallingEdge(GPIO_PinType GPIO_Pin, ACTION_Type enable);
static void GPIO_SetPinRisingEdge(GPIO_PinType GPIO_Pin, ACTION_Type enable);
static void GPIO_SetPinExtIntMask(GPIO_PinType GPIO_Pin, ACTION_Type enable);

/* ======================================  Functions define  ======================================== */
/*!
 * @brief Set several GPIO pins' Pmux and pin function.
 *
 * @param[in] GPIOx: GPIO type structure pointer,x can be A.B.C.D
 * @param[in] config: GPIO_Config type structure pointer
 * @return none
 */
void GPIO_Init(GPIO_Type *GPIOx, const GPIO_ConfigType* config)
{
    DEVICE_ASSERT(IS_GPIO_PERIPH(GPIOx));
    DEVICE_ASSERT(IS_GPIO_PIN(config->GPIO_Pin));
    DEVICE_ASSERT(IS_GPIO_DIR(config->GPIO_Mode.GPIO_Dir));
    DEVICE_ASSERT(IS_GPIO_PINMUX(config->GPIO_Mode.GPIO_Fun));

    CKGEN_Enable(CLK_GPIO, ENABLE);
    CKGEN_SoftReset(SRST_GPIO, ENABLE);

    GPIO_SetGPIOxMODE(GPIOx, config->GPIO_Pin, config->GPIO_Mode.GPIO_Dir); /* GPIO mode config */

    switch (config->GPIO_Mode.GPIO_PuPd)             /* GPIO PuPd config */
    {
    case GPIO_FLOATING:
        GPIO_SetGPIOxPD(GPIOx, config->GPIO_Pin, DISABLE);
        GPIO_SetGPIOxPU(GPIOx, config->GPIO_Pin, DISABLE);
        break;

    case GPIO_PU:
        GPIO_SetGPIOxPD(GPIOx, config->GPIO_Pin, DISABLE);
        GPIO_SetGPIOxPU(GPIOx, config->GPIO_Pin, ENABLE);
        break;

    case GPIO_PD:
        GPIO_SetGPIOxPD(GPIOx, config->GPIO_Pin, ENABLE);
        GPIO_SetGPIOxPU(GPIOx, config->GPIO_Pin, DISABLE);
        break;

    default:
        break;
    }

    GPIO_SetFunc(GPIOx, config->GPIO_Pin, config->GPIO_Mode.GPIO_Fun);
    GPIO_SetGPIOxE4E2x(GPIOx, config->GPIO_Pin, config->GPIO_Driving);      /* GPIO config driving */
}

/*!
 * @brief reset all GPIO: disable interrupt and disable clock.
 *
 * @param[in] none
 * @return none
 */
void GPIO_DeInit(void)
{
    uint8_t i = 0U;

    for (i = 0U; i < GPIO_INT_NUM; i++)
    {
        NVIC_DisableIRQ(s_gpioIRQ[i]);
        NVIC_ClearPendingIRQ(s_gpioIRQ[i]);
        g_gpioCallbackArray[i] = NULL;
    }

    CKGEN_SoftReset(SRST_GPIO, DISABLE);
    CKGEN_Enable(CLK_GPIO, DISABLE);
}

/*!
 * @brief Set GPIO pin level by GPIO_ODR register.
 *
 * @param[in] GPIOx: GPIO type structure pointer,x can be A.B.C.D
 * @param[in] GPIO_Pin: GPIO_Pin type num:
 *                   -GPIO_PIN0
 *                   -GPIO_PIN1
 *                   -GPIO_PIN2
 *                   - ...
 *                   -GPIO_PIN15
 * @param[in] pinVal: Pin level
 *                   -GPIO_LEVEL_LOW
 *                   -GPIO_LEVEL_HIGH
 * @return none
 */
void GPIO_SetPinLevel(GPIO_Type *GPIOx, GPIO_PinType GPIO_Pin, GPIO_LevelType pinLevel)
{
    DEVICE_ASSERT(IS_GPIO_PERIPH(GPIOx));
    DEVICE_ASSERT(IS_GPIO_PIN(GPIO_Pin));

    GPIO_SetGPIOxODRx(GPIOx, GPIO_Pin, pinLevel);
}

/*!
 * @brief Get GPIO pin value by GPIO_IDR register.
 *
 * @param[in] GPIOx: GPIO type structure pointer,x can be A.B.C.D
 * @param[in] GPIO_Pin: GPIO_Pin type num:
 *                   -GPIO_PIN0
 *                   -GPIO_PIN1
 *                   -GPIO_PIN2
 *                   - ...
 *                   -GPIO_PIN15
 * @return pinVal: Pin level
 *                   -GPIO_LEVEL_LOW
 *                   -GPIO_LEVEL_HIGH
 */
GPIO_LevelType GPIO_GetPinLevel(GPIO_Type *GPIOx, GPIO_PinType GPIO_Pin)
{
    uint32_t pinVal = 0U;

    DEVICE_ASSERT(IS_GPIO_PERIPH(GPIOx));
    DEVICE_ASSERT(IS_GPIO_PIN(GPIO_Pin));

    pinVal = ((GPIOx->IDR) >> GPIO_IDR_Pos(GPIO_Pin)) & 0x01U;

    return ((GPIO_LevelType)pinVal);
}

/*!
 * @brief Set Select GPIO pin by GPIO_BSRR register.
 *
 * @param[in] GPIOx: GPIO type structure pointer,x can be A.B.C.D
 * @param[in] GPIO_Pin: GPIO_Pin type num:
 *                   -GPIO_PIN0
 *                   -GPIO_PIN1
 *                   -GPIO_PIN2
 *                   - ...
 *                   -GPIO_PIN15
 * @return none
 */
void GPIO_SetPinBit(GPIO_Type *GPIOx, GPIO_PinType GPIO_Pin)
{
    DEVICE_ASSERT(IS_GPIO_PERIPH(GPIOx));
    DEVICE_ASSERT(IS_GPIO_PIN(GPIO_Pin));

    GPIO_SetGPIOxBSRRx(GPIOx, GPIO_Pin);
}

/*!
 * @brief Reset Select GPIO pin.
 *
 * @param[in] GPIOx: GPIO type structure pointer,x can be A.B.C.D
 * @param[in] GPIO_Pin: GPIO_Pin type num:
 *                   -GPIO_PIN0
 *                   -GPIO_PIN1
 *                   -GPIO_PIN2
 *                   - ...
 *                   -GPIO_PIN15
 * @return none
 */
void GPIO_ResetPinBit(GPIO_Type *GPIOx, GPIO_PinType GPIO_Pin)
{
    DEVICE_ASSERT(IS_GPIO_PERIPH(GPIOx));
    DEVICE_ASSERT(IS_GPIO_PIN(GPIO_Pin));

    GPIO_SetGPIOxBRRx(GPIOx, GPIO_Pin);
}

/*!
 * @brief Set GPIO port value by GPIO_ODR register.
 *
 * @param[in] GPIOx: GPIO type structure pointer,x can be A.B.C.D
 * @param[in] portLevel: GPIOx_ODR register value, it can be 0~0xFFFF
 * @return none
 */
void GPIO_SetPortLevel(GPIO_Type *GPIOx, uint16_t portLevel)
{
    DEVICE_ASSERT(IS_GPIO_PERIPH(GPIOx));

    GPIOx->ODR = portLevel;
}

/*!
 * @brief Get GPIO port level by GPIO_IDR register.
 *
 * @param[in] GPIOx: GPIO type structure pointer,x can be A.B.C.D
 * @return portLevel: GPIOx_IDR register value, it can be 0~0xFFFF
 */
uint16_t GPIO_GetPortLevel(GPIO_Type *GPIOx)
{
    DEVICE_ASSERT(IS_GPIO_PERIPH(GPIOx));

    return (uint16_t)(GPIOx->IDR & 0xFFFFU);
}

/*!
 * @brief Set a GPIO pin's Pmux and pin function.
 *
 * @param[in] GPIOx: GPIO type structure pointer,x can be A.B.C.D
 * @param[in] GPIO_Pin: GPIO_Pin type num:
 *                   -GPIO_PIN0
 *                   -GPIO_PIN1
 *                   -GPIO_PIN2
 *                   - ...
 *                   -GPIO_PIN15
 * @param[in] functionx: GPIO function
 *                   -GPIO_FUN0
 *                   -GPIO_FUN1
 *                   -GPIO_FUN2
 *                   -GPIO_FUN3
 *                   -GPIO_FUN4
 *                   -GPIO_FUN5
 *                   -GPIO_FUN6
 * @return none
 */
void GPIO_SetFunc(GPIO_Type *GPIOx, GPIO_PinType GPIO_Pin, GPIO_FunType functionx)
{
    uint8_t gpioNum = 0U;

    DEVICE_ASSERT(IS_GPIO_PERIPH(GPIOx));
    DEVICE_ASSERT(IS_GPIO_PIN(GPIO_Pin));
    DEVICE_ASSERT(IS_GPIO_PINMUX(functionx));

    switch ((uint32_t)GPIOx)
    {
    case GPIOA_BASE:
        gpioNum = 0U;
        break;

    case GPIOB_BASE:
        gpioNum = 16U;
        break;

    case GPIOC_BASE:
        gpioNum = 32U;
        break;

    case GPIOD_BASE:
        gpioNum = 48U;
        break;
    default:
        break;
    }

    /* Get GPIO number 0~41 */
    gpioNum = gpioNum + (uint8_t)GPIO_Pin;

    /* Set PMUX */
    if (gpioNum < 10U)          /* GPIO 0~9 */
    {
        PMUX_SetPMUXxPFSELx(PMUX0, gpioNum, functionx);
    }
    else if (gpioNum < 20U)     /* GPIO 10~19 */
    {
        gpioNum = gpioNum - 10U;
        PMUX_SetPMUXxPFSELx(PMUX1, gpioNum, functionx);
    }
    else if (gpioNum < 30U)    /* GPIO 20~29 */
    {
        gpioNum = gpioNum - 20U;
        PMUX_SetPMUXxPFSELx(PMUX2, gpioNum, functionx);
    }
    else if (gpioNum < 40U)     /* GPIO 30~39 */
    {
        gpioNum = gpioNum - 30U;
        PMUX_SetPMUXxPFSELx(PMUX3, gpioNum, functionx);
    }
    else if (gpioNum < 50U)     /* GPIO 40~49 */
    {
        gpioNum = gpioNum - 40U;
        PMUX_SetPMUXxPFSELx(PMUX4, gpioNum, functionx);
    }
    else if (gpioNum < 58U)     /* GPIO 50~57 */
    {
        gpioNum = gpioNum - 50U;
        PMUX_SetPMUXxPFSELx(PMUX5, gpioNum, functionx);
    }
    else
    {
        /* do nothing */
    }

    /* Enable input detection */
    GPIO_SetGPIOxIES(GPIOx, GPIO_Pin, ENABLE);
}

/*!
 * @brief Get a GPIO pin's Pmux function.
 *
 * @param[in] GPIOx: GPIO type structure pointer,x can be A.B.C.D
 * @param[in] GPIO_Pin: GPIO_Pin type num:
 *                   -GPIO_PIN0
 *                   -GPIO_PIN1
 *                   -GPIO_PIN2
 *                   - ...
 *                   -GPIO_PIN15
 * @return GPIO pin Pmux function
 */
GPIO_FunType GPIO_GetPinFunc(GPIO_Type *GPIOx, GPIO_PinType GPIO_Pin)
{
    uint32_t pinFunc = 0U;
    uint8_t gpioNum = 0U;

    DEVICE_ASSERT(IS_GPIO_PERIPH(GPIOx));
    DEVICE_ASSERT(IS_GPIO_PIN(GPIO_Pin));

    switch((uint32_t)GPIOx)
    {
    case GPIOA_BASE:
        gpioNum = 0U;
        break;

    case GPIOB_BASE:
        gpioNum = 16U;
        break;

    case GPIOC_BASE:
        gpioNum = 32U;
        break;

    case GPIOD_BASE:
        gpioNum = 48U;

        break;
    default:
        break;
    }

    /* Get GPIO number 0~57 */
    gpioNum = gpioNum + (uint8_t)GPIO_Pin;

    /* Set PMUX */
    if (gpioNum < 10U)          /* GPIO 0~9 */
    {
        pinFunc = (*PMUX0) >> (PMUX_PINMUX_Pos(gpioNum));
    }
    else if (gpioNum < 20U)     /* GPIO 10~19 */
    {
        gpioNum = gpioNum - 10U;
        pinFunc = (*PMUX1) >> (PMUX_PINMUX_Pos(gpioNum));
    }
    else if (gpioNum < 30U)     /* GPIO 20~29 */
    {
        gpioNum = gpioNum - 20U;
        pinFunc = (*PMUX2) >> (PMUX_PINMUX_Pos(gpioNum));
    }
    else if (gpioNum < 40U)     /* GPIO 30~39 */
    {
        gpioNum = gpioNum - 30U;
        pinFunc = (*PMUX3) >> (PMUX_PINMUX_Pos(gpioNum));
    }
    else if (gpioNum < 50U)     /* GPIO 40~49 */
    {
        gpioNum = gpioNum - 40U;
        pinFunc = (*PMUX4) >> (PMUX_PINMUX_Pos(gpioNum));
    }
    else if (gpioNum < 58U)     /* GPIO 50~57 */
    {
        gpioNum = gpioNum - 50U;
        pinFunc = (*PMUX5) >> (PMUX_PINMUX_Pos(gpioNum));
    }
    else
    {

    }

    return (GPIO_FunType)(pinFunc & 0x07U);
}

/*!
 * @brief Set several GPIO pins' Pmux( pin function ).
 *
 * @param[in] GPIOx: GPIO type structure pointer,x can be A.B.C.D
 * @param[in] pinMask: GPIO pin mask value indicate which GPIO need to be modifyed
 * @param[in] functionx: GPIO function
 *                   -GPIO_FUN0
 *                   -GPIO_FUN1
 *                   -GPIO_FUN2
 *                   -GPIO_FUN3
 *                   -GPIO_FUN4
 *                   -GPIO_FUN5
 *                   -GPIO_FUN6
 * @return none
 */
void GPIO_SetFuncGroup(GPIO_Type *GPIOx, uint16_t pinMask, GPIO_FunType functionx)
{
    uint16_t pinPos = 0x00U, pos = 0x00U;

    DEVICE_ASSERT(IS_GPIO_PERIPH(GPIOx));
    DEVICE_ASSERT(IS_GPIO_PINMUX(functionx));

    if (pinMask & 0xFFFFU)
    {
        for (pinPos = 0x00U; pinPos < 0x10U; pinPos++)
        {
            pos = ((uint16_t)0x01U) << pinPos;
            if (pinMask & pos)
            {
                GPIO_SetFunc(GPIOx, (GPIO_PinType)pinPos, functionx);
                /* Enable input detection */
                GPIO_SetGPIOxIES(GPIOx, (GPIO_PinType)pinPos, ENABLE);
            }
            else
            {
                /* do nothing */
            }
        }
    }
    else
    {
        /* do nothing */
    }
}

/*!
 * @brief Set a GPIO pin's mode, input or output.
 *
 * @param[in] GPIOx: GPIO type structure pointer,x can be A.B.C.D
 * @param[in] GPIO_Pin: GPIO_Pin type num:
 *                   -GPIO_PIN0
 *                   -GPIO_PIN1
 *                   -GPIO_PIN2
 *                   - ...
 *                   -GPIO_PIN15
 * @param[in] pinDir: GPIO direction, input or output
 *                   -GPIO_IN
 *                   -GPIO_OUT
 * @return none
 */
void GPIO_SetDir(GPIO_Type *GPIOx, GPIO_PinType GPIO_Pin, GPIO_DirType pinDir)
{
    DEVICE_ASSERT(IS_GPIO_PERIPH(GPIOx));
    DEVICE_ASSERT(IS_GPIO_PIN(GPIO_Pin));
    DEVICE_ASSERT(IS_GPIO_DIR(pinDir));

    GPIO_SetGPIOxMODE(GPIOx, GPIO_Pin, pinDir);
}

/*!
 * @brief Get a GPIO pin's direction, input or output.
 *
 * @param[in] GPIOx: GPIO type structure pointer,x can be A.B.C.D
 * @param[in] GPIO_Pin: GPIO_Pin type num:
 *                   -GPIO_PIN0
 *                   -GPIO_PIN1
 *                   -GPIO_PIN2
 *                   - ...
 *                   -GPIO_PIN15
 * @return GPIO pin's direction
 *                   -GPIO_IN
 *                   -GPIO_OUT
 */
GPIO_DirType GPIO_GetDir(GPIO_Type *GPIOx, GPIO_PinType GPIO_Pin)
{
    uint32_t regVal = 0U;

    DEVICE_ASSERT(IS_GPIO_PERIPH(GPIOx));
    DEVICE_ASSERT(IS_GPIO_PIN(GPIO_Pin));

    regVal = GPIO_GetGPIOxCR(GPIOx);
    return (GPIO_DirType)((regVal >> GPIO_CR_Pos(GPIO_Pin)) & 0x01U);
}


/*!
 * @brief Set several GPIO pins' pin mode.
 *
 * @param[in] GPIOx: GPIO type structure pointer,x can be A.B.C.D
 * @param[in] pinMask: GPIO pin mask value indicate which gpios need to be modifyed
 * @param[in] pinDir: GPIO direction, input or output
 *                   -GPIO_IN
 *                   -GPIO_OUT
 * @return none
 */
void GPIO_SetDirGroup(GPIO_Type *GPIOx, uint16_t pinMask, GPIO_DirType pinDir)
{
    uint16_t currentMode = (uint16_t)pinDir, pinPos = 0x00U, pos = 0x00U;
    uint32_t tmpReg = 0x00U;

    DEVICE_ASSERT(IS_GPIO_PERIPH(GPIOx));
    DEVICE_ASSERT(IS_GPIO_DIR(pinDir));

    if ((pinMask & 0xFFFFU) > 0U)
    {
        tmpReg = GPIOx->CR;
        for (pinPos = 0x00U; pinPos < 0x10U; pinPos++)
        {
            pos = ((uint16_t)0x01U) << pinPos;
            if (pinMask & pos)
            {
                tmpReg &= ~pos;
                /* Write the mode configuration in the corresponding bits */
                tmpReg |= (currentMode << pinPos);
            }
        }
        GPIOx->CR = tmpReg;
    }
}

/*!
 * @brief Set GPIO pin's pull-up resistor.
 *
 * @param[in] GPIOx: GPIO type structure pointer,x can be A.B.C.D
 * @param[in] GPIO_Pin: GPIO_Pin type num:
 *                   -GPIO_PIN0
 *                   -GPIO_PIN1
 *                   -GPIO_PIN2
 *                   - ...
 *                   -GPIO_PIN15
 * @param[in] enable: enable or disable pull-up resistor
 *                   -ENABLE
 *                   -DISABLE
 * @return none
 */
void GPIO_SetPullup(GPIO_Type *GPIOx, GPIO_PinType GPIO_Pin, ACTION_Type enable)
{
    DEVICE_ASSERT(IS_GPIO_PERIPH(GPIOx));
    DEVICE_ASSERT(IS_GPIO_PIN(GPIO_Pin));

    GPIO_SetGPIOxPU(GPIOx, GPIO_Pin, enable);
}

/*!
 * @brief Set GPIO pin's pull-down resistor.
 *
 * @param[in] GPIOx: GPIO type structure pointer,x can be A.B.C.D
 * @param[in] GPIO_Pin: GPIO_Pin type num:
 *                   -GPIO_PIN0
 *                   -GPIO_PIN1
 *                   -GPIO_PIN2
 *                   - ...
 *                   -GPIO_PIN15
 * @param[in] enable: enable or disable pull-down resistor
 *                   -ENABLE
 *                   -DISABLE
 * @return none
 */
void GPIO_SetPulldown(GPIO_Type *GPIOx, GPIO_PinType GPIO_Pin, ACTION_Type enable)
{
    DEVICE_ASSERT(IS_GPIO_PERIPH(GPIOx));
    DEVICE_ASSERT(IS_GPIO_PIN(GPIO_Pin));

    GPIO_SetGPIOxPD(GPIOx, GPIO_Pin, enable);
}

/*!
 * @brief Set a GPIO pin's driving ability.
 *
 * @param[in] GPIOx: GPIO type structure pointer,x can be A.B.C.D
 * @param[in] GPIO_Pin: GPIO_Pin type num:
 *                   -GPIO_PIN0
 *                   -GPIO_PIN1
 *                   -GPIO_PIN2
 *                   - ...
 *                   -GPIO_PIN15
 * @param[in] E4E2Value: Driving current select
 *                   -GPIO_DRIVING_4MA
 *                   -GPIO_DRIVING_8MA
 *                   -GPIO_DRIVING_12MA
 *                   -GPIO_DRIVING_16MA
 * @return none
 */
void GPIO_SetDrivingAbility(GPIO_Type *GPIOx, GPIO_PinType GPIO_Pin, GPIO_DrivingType E4E2Value)
{
    DEVICE_ASSERT(IS_GPIO_PERIPH(GPIOx));
    DEVICE_ASSERT(IS_GPIO_PIN(GPIO_Pin));

    GPIO_SetGPIOxE4E2x(GPIOx, GPIO_Pin, E4E2Value);
}

/*!
 * @brief Set GPIO as high-Z state or floating input state.
 *
 * @param[in] GPIOx: GPIO type structure pointer,x can be A.B.C.D
 * @param[in] GPIO_Pin: GPIO_Pin type num:
 *                   -GPIO_PIN0
 *                   -GPIO_PIN1
 *                   -GPIO_PIN2
 *                   - ...
 *                   -GPIO_PIN15
 * @param[in] enable: enable or disable high-z state. If disable high-z, IO will be set to floating input.
 *                   -ENABLE
 *                   -DISABLE
 * @return none
 */
void GPIO_SetHighZ(GPIO_Type *GPIOx, GPIO_PinType GPIO_Pin, ACTION_Type enable)
{
    /* Set pinmux to GPIO */
    GPIO_SetFunc(GPIOx, GPIO_Pin, GPIO_FUN6);

    /* Disable pull-up and pull-down */
    GPIO_SetGPIOxPU(GPIOx, GPIO_Pin, DISABLE);
    GPIO_SetGPIOxPD(GPIOx, GPIO_Pin, DISABLE);

    /* Disable output */
    GPIO_SetGPIOxMODE(GPIOx, GPIO_Pin, GPIO_IN);

    /* Disable input or enable input */
    GPIO_SetGPIOxIES(GPIOx, GPIO_Pin, (!enable));
}

/*!
 * @brief Enable a GPIO pin's external interrupt and trigger condition.
 *
 * @param[in] GPIOx: GPIO type structure pointer,x can be A.B.C.D
 * @param[in] GPIO_Pin: GPIO_Pin type num:
 *                   -GPIO_PIN0
 *                   -GPIO_PIN1
 *                   -GPIO_PIN2
 *                   - ...
 *                   -GPIO_PIN15
 * @param[in] risingFallingEdge: GPIO's external interrupt set,value can be
 *                   -EXTI_TRIGGER_FALLING
 *                   -EXTI_TRIGGER_RISING
 *                   -EXTI_TRIGGER_RISING_FALLING
 * @return none
 */
void GPIO_EnableExtInterrupt(GPIO_Type *GPIOx, GPIO_PinType GPIO_Pin, EXTI_TriggerType risingFallingEdge)
{
    uint8_t gpioPin = (uint8_t)GPIO_Pin;

    DEVICE_ASSERT(IS_GPIO_PERIPH(GPIOx));
    DEVICE_ASSERT(IS_GPIO_PIN(GPIO_Pin));
    DEVICE_ASSERT(IS_EXTI_TRIGGER(risingFallingEdge));

    /* Disable pin interrupt mask */
    GPIO_SetPinExtIntMask(GPIO_Pin, DISABLE);

    /* Set pin as external interrupt source */
    GPIO_SetPinExtIntSource(GPIOx, GPIO_Pin);

    /* Set pin external interrupt trigger mode */
    if (risingFallingEdge == EXTI_TRIGGER_FALLING)
    {
        GPIO_SetPinRisingEdge(GPIO_Pin, DISABLE);
        GPIO_SetPinFallingEdge(GPIO_Pin, ENABLE);
    }
    else if (risingFallingEdge == EXTI_TRIGGER_RISING)
    {
        GPIO_SetPinRisingEdge(GPIO_Pin, ENABLE);
        GPIO_SetPinFallingEdge(GPIO_Pin, DISABLE);
    }
    else if (risingFallingEdge == EXTI_TRIGGER_RISING_FALLING)
    {
        GPIO_SetPinFallingEdge(GPIO_Pin, ENABLE);
        GPIO_SetPinRisingEdge(GPIO_Pin, ENABLE);
    }
    else
    {

    }

    GPIO_ClearPendingExtInterrupt(GPIO_Pin);
    /* Enable GPIO external interrupt */
    if (gpioPin > 8U)
    {
        NVIC_ClearPendingIRQ(EXTI9_15_IRQn);
        NVIC_EnableIRQ(EXTI9_15_IRQn);
    }
    else if (gpioPin > 2U)
    {
        NVIC_ClearPendingIRQ(EXTI3_8_IRQn);
        NVIC_EnableIRQ(EXTI3_8_IRQn);
    }
    else
    {
        switch (gpioPin)
        {
        case 0:
            NVIC_ClearPendingIRQ(EXTI0_IRQn);
            NVIC_EnableIRQ(EXTI0_IRQn);
            break;

        case 1:
            NVIC_ClearPendingIRQ(EXTI1_IRQn);
            NVIC_EnableIRQ(EXTI1_IRQn);
            break;

        case 2:
            NVIC_ClearPendingIRQ(EXTI2_IRQn);
            NVIC_EnableIRQ(EXTI2_IRQn);
            break;

        default :
            break;
        }
    }
    /* Set pin interrupt mask */
    GPIO_SetPinExtIntMask(GPIO_Pin, ENABLE);
}

/*!
 * @brief Disable a GPIO pin's external interrupt and trigger condition.
 *
 * @param[in] GPIO_Pin: GPIO_Pin type num:
 *                   -GPIO_PIN0
 *                   -GPIO_PIN1
 *                   -GPIO_PIN2
 *                   - ...
 *                   -GPIO_PIN15
 * @return  none
 */
void GPIO_DisableExtInterrupt(GPIO_PinType GPIO_Pin)
{
    DEVICE_ASSERT(IS_GPIO_PIN(GPIO_Pin));

    /* Reset pin interrupt mask */
    GPIO_SetPinExtIntMask(GPIO_Pin, DISABLE);
}

/*!
 * @brief Set a GPIO pin's external interrupt source.
 *
 * @param[in] GPIOx: GPIO type structure pointer,x can be A.B.C.D
 * @param[in] GPIO_Pin: GPIO_Pin type num:
 *                   -GPIO_PIN0
 *                   -GPIO_PIN1
 *                   -GPIO_PIN2
 *                   - ...
 *                   -GPIO_PIN15
 * @return none
 */
static void GPIO_SetPinExtIntSource(GPIO_Type *GPIOx, GPIO_PinType GPIO_Pin)
{
    uint8_t extiValue = 0U;

    DEVICE_ASSERT(IS_GPIO_PERIPH(GPIOx));
    DEVICE_ASSERT(IS_GPIO_PIN(GPIO_Pin));

    switch((uint32_t)GPIOx)
    {
    case GPIOA_BASE:
        extiValue = 0U;
        break;

    case GPIOB_BASE:
        extiValue = 1U;
        break;

    case GPIOC_BASE:
        extiValue = 2U;
        break;

    case GPIOD_BASE:
        extiValue = 3U;
        break;

    default:
        break;
    }

    if (GPIO_Pin < GPIO_PIN4)                                      /* Pin0~3 */
    {
        MODIFY_REG32(EXTI->EXTICR0, EXTI_EXTICR_Msk(GPIO_Pin), EXTI_EXTICR_Pos(GPIO_Pin), extiValue);
    }
    else if ((GPIO_PIN4 <= GPIO_Pin) && (GPIO_Pin < GPIO_PIN8))    /* Pin4~7 */
    {
        MODIFY_REG32(EXTI->EXTICR1, EXTI_EXTICR_Msk(GPIO_Pin), EXTI_EXTICR_Pos(GPIO_Pin), extiValue);
    }
    else if ((GPIO_PIN8 <= GPIO_Pin) && (GPIO_Pin < GPIO_PIN12))   /* Pin8~11 */
    {
        MODIFY_REG32(EXTI->EXTICR2, EXTI_EXTICR_Msk(GPIO_Pin), EXTI_EXTICR_Pos(GPIO_Pin), extiValue);
    }
    else if ((GPIO_PIN12 <= GPIO_Pin) && (GPIO_Pin <= GPIO_PIN15))  /* Pin12~15 */
    {
        MODIFY_REG32(EXTI->EXTICR3, EXTI_EXTICR_Msk(GPIO_Pin), EXTI_EXTICR_Pos(GPIO_Pin), extiValue);
    }
    else
    {
        /* do nothing */
    }
}

/*!
 * @brief Get GPIO_Type pointer, which is the source of pending external interrupt.
 *
 * @param[in] GPIO_Pin: GPIO_Pin type num, which is the pin of pending inerrupt
 *                   -GPIO_PIN0
 *                   -GPIO_PIN1
 *                   -GPIO_PIN2
 *                   - ...
 *                   -GPIO_PIN15
 * @return GPIO_Type pointer, which is the source of pending external interrupt
 *                   -GPIOA
 *                   -GPIOB
 *                   -GPIOC
 *                   -GPIOD
 */
static GPIO_Type * GPIO_GetPendingExtIntSource(GPIO_PinType GPIO_Pin)
{
    uint32_t gpioGroup = 0U;
    GPIO_Type *GPIOx = GPIOA;

    DEVICE_ASSERT(IS_GPIO_PIN(GPIO_Pin));

    /* Get interrupt source in EXTICR register */
    if(GPIO_Pin < GPIO_PIN4)      /* Pin0~3 */
    {
        gpioGroup = (EXTI->EXTICR0 >> EXTI_EXTICR_Pos(GPIO_Pin)) & 0x0FU;
    }
    else if((GPIO_PIN4 <= GPIO_Pin) && (GPIO_Pin < GPIO_PIN8))      /* Pin4~7 */
    {
        gpioGroup = (EXTI->EXTICR1 >> EXTI_EXTICR_Pos(GPIO_Pin)) & 0x0FU;
    }
    else if((GPIO_PIN8 <= GPIO_Pin) && (GPIO_Pin < GPIO_PIN12))     /* Pin8~11 */
    {
        gpioGroup = (EXTI->EXTICR2 >> EXTI_EXTICR_Pos(GPIO_Pin)) & 0x0FU;
    }
    else if((GPIO_PIN12 <= GPIO_Pin) && (GPIO_Pin <= GPIO_PIN15))  /* Pin12~15 */
    {
        gpioGroup = (EXTI->EXTICR3 >> EXTI_EXTICR_Pos(GPIO_Pin)) & 0x0FU;
    }
    else
    {
        /* do nothing */
    }

    /* Get GPIO_Type pointer by gpioGroup */
    switch(gpioGroup)
    {
    case 0:
        GPIOx = GPIOA;
        break;

    case 1:
        GPIOx = GPIOB;
        break;

    case 2:
        GPIOx = GPIOC;
        break;

    case 3:
        GPIOx = GPIOD;
        break;

    default:
        break;
    }

    return GPIOx;
}

/*!
 * @brief Set a GPIO pin's external interrupt falling edge sensitive.
 *
 * @param[in] GPIO_Pin: GPIO_Pin type num:
 *                   -GPIO_PIN0
 *                   -GPIO_PIN1
 *                   -GPIO_PIN2
 *                   - ...
 *                   -GPIO_PIN15
 * @param[in] enable: GPIO pin falling edge enable or disable
 *                   -ENABLE
 *                   -DISABLE
 * @return none
 */
static void GPIO_SetPinFallingEdge(GPIO_PinType GPIO_Pin, ACTION_Type enable)
{
    DEVICE_ASSERT(IS_GPIO_PIN(GPIO_Pin));

    MODIFY_REG32(EXTI->FTSR, EXTI_FTSR_Msk(GPIO_Pin),  EXTI_FTSR_Pos(GPIO_Pin), enable);
}

/*!
 * @brief Set a GPIO pin's external interrupt rising edge sensitive.
 *
 * @param[in] GPIO_Pin: GPIO_Pin type num:
 *                   -GPIO_PIN0
 *                   -GPIO_PIN1
 *                   -GPIO_PIN2
 *                   - ...
 *                   -GPIO_PIN15
 * @param[in] enable: GPIO pin rising edge enable or disable
 *                   -ENABLE
 *                   -DISABLE
 * @return none
 */
static void GPIO_SetPinRisingEdge(GPIO_PinType GPIO_Pin, ACTION_Type enable)
{
    DEVICE_ASSERT(IS_GPIO_PIN(GPIO_Pin));

    MODIFY_REG32(EXTI->RTSR, EXTI_RTSR_Msk(GPIO_Pin),  EXTI_RTSR_Pos(GPIO_Pin), enable);
}

/*!
 * @brief Set a GPIO pin's external interrupt mask.
 *
 * @param[in] GPIO_Pin: GPIO_Pin type num:
 *                   -GPIO_PIN0
 *                   -GPIO_PIN1
 *                   -GPIO_PIN2
 *                   - ...
 *                   -GPIO_PIN15
 * @param[in] enable: GPIO pin external interrupt mask
 *                   -ENABLE : external interrupt active
 *                   -DISABLE: external interrupt inactive
 * @return none
 */
static void GPIO_SetPinExtIntMask(GPIO_PinType GPIO_Pin, ACTION_Type enable)
{
    DEVICE_ASSERT(IS_GPIO_PIN(GPIO_Pin));

    MODIFY_REG32(EXTI->IMR, EXTI_IMR_Msk(GPIO_Pin),  EXTI_IMR_Pos(GPIO_Pin), enable);
}

/*!
 * @brief Get a GPIO pin's pending external interrupt IRQ number, a 16bit mask which represents the GPIO's group internal position.
 *
 * @param[in] none
 * @return GPIO's Pending external interrupt 16 bits mask
 */
uint16_t GPIO_GetPendingExtInterrupt(void)
{
    return (uint16_t)(EXTI->PR & 0xFFFFU);
}

/*!
 * @brief Clear a GPIO pin's external interrupt pending flag.
 *
 * @param[in] GPIO_Pin: GPIO_Pin type num:
 *                   -GPIO_PIN0
 *                   -GPIO_PIN1
 *                   -GPIO_PIN2
 *                   - ...
 *                   -GPIO_PIN15
 * @return none
 */
void GPIO_ClearPendingExtInterrupt(GPIO_PinType GPIO_Pin)
{
    DEVICE_ASSERT(IS_GPIO_PIN(GPIO_Pin));

    EXTI->PR = (0x01UL << EXTI_PR_Pos(GPIO_Pin));
}

/*!
 * @brief Set GPIO interrupt callback function, Pin0~2 has the independent interrupt vector,
 *         Pin3~8 share the same interrupt vector, and Pin9~15 share the same interrupt vector.
 *
 * @param[in] GPIO_Pin: GPIO_Pin type num:
 *                   -GPIO_PIN0
 *                   -GPIO_PIN1
 *                   -GPIO_PIN2
 *                   - ...
 *                   -GPIO_PIN15
 * @param[in] callback: GPIO interrupt callback function, which will be called in EXTIx_IRQHandler()
 * @return none
 */
void GPIO_SetCallback(GPIO_PinType GPIO_Pin, const DeviceCallback_Type callback)
{
    uint8_t callbackIndex = 0U;

    DEVICE_ASSERT(IS_GPIO_PIN(GPIO_Pin));

    /* set gpio callback function pointer */
    if (GPIO_Pin > GPIO_PIN8)
    {
        callbackIndex = 4U;  /* 9~15 */
    }
    else if (GPIO_Pin > GPIO_PIN2)
    {
        callbackIndex = 3U;  /* 3~8 */
    }
    else
    {
        callbackIndex = (uint8_t)GPIO_Pin;
    }

    /* Set GPIO interrupt callback */
    g_gpioCallbackArray[callbackIndex] = callback;
}

/*!
 * @brief override the EXTI0_IRQHandler.
 *
 * @param[in] none
 * @return none
 */
void EXTI0_IRQHandler(void)
{
    GPIO_Type *GPIOx = GPIOA;

    GPIOx = GPIO_GetPendingExtIntSource(GPIO_PIN0);

    GPIO_ClearPendingExtInterrupt(GPIO_PIN0);

    if (g_gpioCallbackArray[0U])
    {
        g_gpioCallbackArray[0U](GPIOx, (uint32_t)GPIO_PIN0, 0U);
    }
}

/*!
 * @brief override the EXTI1_IRQHandler.
 *
 * @param[in] none
 * @return none
 */
void EXTI1_IRQHandler(void)
{
    GPIO_Type *GPIOx = GPIOA;

    GPIOx = GPIO_GetPendingExtIntSource(GPIO_PIN1);

    GPIO_ClearPendingExtInterrupt(GPIO_PIN1);

    if (g_gpioCallbackArray[1U])
    {
        g_gpioCallbackArray[1U](GPIOx, (uint32_t)GPIO_PIN1, 0U);
    }
}

/*!
 * @brief override the EXTI2_IRQHandler.
 *
 * @param[in] none
 * @return none
 */
void EXTI2_IRQHandler(void)
{
    GPIO_Type *GPIOx = GPIOA;

    GPIOx = GPIO_GetPendingExtIntSource(GPIO_PIN2);

    GPIO_ClearPendingExtInterrupt(GPIO_PIN2);

    if (g_gpioCallbackArray[2U])
    {
        g_gpioCallbackArray[2U](GPIOx, (uint32_t)GPIO_PIN2, 0U);
    }
}

/*!
 * @brief override the EXTI3_8_IRQHandler.
 *
 * @param[in] none
 * @return none
 */
void EXTI3_8_IRQHandler(void)
{
    GPIO_Type *GPIOx = GPIOA;
    uint16_t pendingIRQMask = 0U, pinMask = 0U, pin = 0U;

    pendingIRQMask = GPIO_GetPendingExtInterrupt();

    for(pin = 3U; pin < 9U; pin++)
    {
        pinMask = GPIO_BIT0 << pin;
        if(pendingIRQMask & pinMask)
        {
            GPIOx = GPIO_GetPendingExtIntSource((GPIO_PinType)pin);
            GPIO_ClearPendingExtInterrupt((GPIO_PinType)pin);
            if (g_gpioCallbackArray[3U])
            {
                g_gpioCallbackArray[3U](GPIOx, (uint32_t)pin, 0U);
            }
        }
    }
}

/*!
 * @brief override the EXTI9_15_IRQHandler.
 *
 * @param[in] none
 * @return none
 */
void EXTI9_15_IRQHandler(void)
{
    GPIO_Type *GPIOx = GPIOA;
    uint16_t pendingIRQMask = 0U, pinMask = 0U, pin = 0U;

    pendingIRQMask = GPIO_GetPendingExtInterrupt();

    for(pin = 9U; pin < 16U; pin++)
    {
        pinMask = GPIO_BIT0 << pin;
        if(pendingIRQMask & pinMask)
        {
            GPIOx = GPIO_GetPendingExtIntSource((GPIO_PinType)pin);
            GPIO_ClearPendingExtInterrupt((GPIO_PinType)pin);
            if (g_gpioCallbackArray[4U])
            {
                g_gpioCallbackArray[4U](GPIOx, (uint32_t)pin, 0U);
            }
        }
    }
}

/* =============================================  EOF  ============================================== */
